/**
 * $version:  0.1 
 * $Date: 2011-01-21 
 *
 * Copyright (C) 2010-2011 Jawa Software. All rights reserved.
 *
 */
package org.jawa.database;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import org.jawa.util.ClassUtils;
import org.jawa.util.JawaGlobals;

/**
 * 
 * 数据库连接管理类，所有方法都是静态的，可以方便的访问数据库连接。
 * 
 * @author Y-N
 */
public class DbConnectionManager {

	private static final Logger Log = LoggerFactory.getLogger(DbConnectionManager.class);

	private static ConnectionProvider connectionProvider;

	private static final Object providerLock = new Object();

	// 是否支持事务
	private static boolean transactionsSupported;
	// 是否支持大文本字段
	private static boolean streamTextRequired;
	// 是否支持 Statement.setMaxRows() 方法
	private static boolean maxRowsSupported;
	// 是否支持 rs.setFetchSize() 方法
	private static boolean fetchSizeSupported;
	// 是否支持子查询
	private static boolean subqueriesSupported;
	// 是否支持滚动结果集
	private static boolean scrollResultsSupported;
	// 是否支持批量更新
	private static boolean batchUpdatesSupported;
	// 是否支持 Statement.setFetchSize() 方法
	private static boolean stmt_fetchSizeSupported = true;

	// 数据库类型
	private static DatabaseType databaseType = DatabaseType.unknown;

	private static SchemaManager schemaManager = new SchemaManager();

	/**
	 * 从当前的连接池提供程序中返回一个连接。如果得不到连接将抛出一个异常。 (一个不带事务的连接，既 auto commit 值为 true )
	 * 
	 * @return 数据库连接
	 * @throws SQLException
	 *             数据库连接没有取到或是获取时发生异常.
	 */
	public static Connection getConnection() throws SQLException {
		if (connectionProvider == null) {
			synchronized (providerLock) {
				if (connectionProvider == null) {
					String className = JawaGlobals.getXMLProperty("database.connectionProvider.className");
					if (className != null) {
						// 尝试加载类
						try {
							Class conClass = ClassUtils.forName(className);
							setConnectionProvider((ConnectionProvider) conClass.newInstance());
						} catch (Exception e) {
							Log.warn("根据配制文件中的连接池提供程序'" + className + "'创建连接池失败. " + "尝试默认的连接池提供程序.", e);
							setConnectionProvider(new DefaultConnectionProvider());
						}
					} else {
						setConnectionProvider(new DefaultConnectionProvider());
					}
				}
			}
		}

		// TODO: 下面的变量值，可以修改成从配置文件获取
		Integer retryCnt = 1;
		Integer retryMax = 10; // 最多尝试10次
		Integer retryWait = 250; // 毫秒
		Connection con = null;
		SQLException lastException = null;
		do {
			try {
				con = connectionProvider.getConnection();
				if (con != null) {
					return con;
				}
			} catch (SQLException e) {
				lastException = e;
				Log.info("无法获取数据库连接. " + "(第 " + retryCnt + " 次尝试,共尝试(" + retryMax + ").", e);
			}
			try {
				Thread.sleep(retryWait);
			} catch (Exception e) {
				// 忽略
			}
			retryCnt++;
		} while (retryCnt < retryMax);
		throw new SQLException("ConnectionManager.getConnection() " + "重试 " + retryCnt + " 次后仍未获取数据库连接. "
				+ "最后一次尝试获取数据库连接时发生如下异常: " + lastException);
	}

	/**
	 * 从当前活动的连接提供者返回一个数据库连接 .(如果数据库支持事务，自动提交将设置为false).
	 * 
	 * @return 带事务的数据库连接.
	 * @throws SQLException
	 *             数据库异常.
	 */
	public static Connection getTransactionConnection() throws SQLException {
		Connection con = getConnection();
		if (isTransactionsSupported()) {
			con.setAutoCommit(false);
		}
		return con;
	}

	/**
	 * 关闭一个 Statement 和一个带事务的数据库连接。不过事务回滚或提交取决于 <code> abortTransaction </code> 值.
	 * 
	 * @param pstmt
	 *            要关闭的 statement.
	 * @param con
	 *            要关闭的数据库连接.
	 * @param abortTransaction
	 *            如果为真事务应该被回滚
	 */
	public static void closeTransactionConnection(Statement pstmt, Connection con, boolean abortTransaction) {
		closeStatement(pstmt);
		closeTransactionConnection(con, abortTransaction);
	}

	/**
	 * 关闭一个带事务的数据库连接。不过，回滚 事务或提交它取决于<code> abortTransaction </code>.
	 * 
	 * @param con
	 *            要关闭的数据库连接.
	 * @param abortTransaction
	 *            如果为真事务应该被回滚.
	 */
	public static void closeTransactionConnection(Connection con, boolean abortTransaction) {
		// 回滚或是提交事务.
		if (isTransactionsSupported()) {
			try {
				if (abortTransaction) {
					con.rollback();
				} else {
					con.commit();
				}
			} catch (Exception e) {
				Log.error(e.getMessage(), e);
			}
			// 将事务置为正常状态下的自动提交方式.
			try {
				con.setAutoCommit(true);
			} catch (Exception e) {
				Log.error(e.getMessage(), e);
			}
		}
		closeConnection(con);
	}

	/**
	 * 关闭一个结果集。如下面的例子:
	 * 
	 * <pre>
	 *  public void doSomething(Connection con) {
	 *      ResultSet rs = null;
	 *      PreparedStatement pstmt = null;
	 *      try {
	 *          pstmt = con.prepareStatement("select * from blah");
	 *          rs = pstmt.executeQuery();
	 *          ....
	 *      }
	 *      catch (SQLException sqle) {
	 *          Log.error(sqle);
	 *      }
	 *      finally {
	 *          ConnectionManager.closeResultSet(rs);
	 *          ConnectionManager.closePreparedStatement(pstmt);
	 *      }
	 * }
	 * </pre>
	 * 
	 * @param rs
	 *            要关闭的结果集.
	 */
	public static void closeResultSet(ResultSet rs) {
		if (rs != null) {
			try {
				rs.close();
			} catch (SQLException e) {
				Log.error(e.getMessage(), e);
			}
		}
	}

	/**
	 * 关闭一个Statement。如下面的例子:
	 * 
	 * <pre>
	 *  public void doSomething(Connection con) {
	 *      Statement stmt = null;
	 *      try {
	 *          pstmt = con.createStatement();
	 *          ....
	 *      }
	 *      catch (SQLException sqle) {
	 *          Log.error(sqle);
	 *      }
	 *      finally {
	 *          ConnectionManager.closeStatement(stmt);
	 *      }
	 * }
	 * </pre>
	 * 
	 * @param stmt
	 *            要关闭的 statement.
	 */
	public static void closeStatement(Statement stmt) {
		if (stmt != null) {
			try {
				stmt.close();
			} catch (Exception e) {
				Log.error(e.getMessage(), e);
			}
		}
	}

	/**
	 * 关闭 一个结果集和一个Statement 。如下面的例子:
	 * 
	 * <pre>
	 *  public void doSomething(Connection con) {
	 *      ResultSet rs = null;
	 *      Statement stmt = null;
	 *      try {
	 *          stmt = con.createStatement();
	 *          rs = stmt.executeQuery("select * from blah");
	 *          ....
	 *      }
	 *      catch (SQLException sqle) {
	 *          Log.error(sqle);
	 *      }
	 *      finally {
	 *          ConnectionManager.closeStatement(rs,stmt);
	 *      }
	 * }
	 * </pre>
	 * 
	 * @param rs
	 *            要关闭的结果集.
	 * @param stmt
	 *            要关闭的 statement.
	 */
	public static void closeStatement(ResultSet rs, Statement stmt) {
		closeResultSet(rs);
		closeStatement(stmt);
	}

	/**
	 * 关闭一个预编译的 Statement。如下面的例子:
	 * 
	 * <pre>
	 *  public void doSomething(Connection con) {
	 *      PreparedStatement pstmt = null;
	 *      try {
	 *          pstmt = con.prepareStatement("select * from dual");
	 *          pstmt.executeUpdate();
	 *          ...
	 *          <b>ConnectionManager.fastcloseStmt(pstmt);</b>
	 *          pstmt = con.prepareStatement("select * from blah");
	 *          ...
	 *      }
	 *      ...
	 * }
	 * </pre>
	 * 
	 * @param stmt
	 *            要关闭的 statement.
	 */
	public static void fastcloseStmt(Statement stmt) throws SQLException {
		stmt.close();
	}

	/**
	 * 关闭 一个结果集和一个预编译的 Statement 。如下面的例子:
	 * 
	 * <pre>
	 *  public void doSomething(Connection con) {
	 *      PreparedStatement pstmt = null;
	 *      try {
	 *          pstmt = con.prepareStatement("select * from blah");
	 *          rs = pstmt.executeQuery();
	 *          ...
	 *          <b>ConnectionManager.fastcloseStmt(rs, pstmt);</b>
	 *          pstmt = con.prepareStatement("select * from blah");
	 *          ...
	 *      }
	 *      ...
	 * }
	 * </pre>
	 * 
	 * @param rs
	 *            要关闭的结果集.
	 * @param stmt
	 *            要关闭的 statement.
	 */
	public static void fastcloseStmt(ResultSet rs, Statement stmt) throws SQLException {
		rs.close();
		stmt.close();
	}

	/**
	 * 关闭一个结果集,一个Statement和一个数据库连接 。 如下面的例子:
	 * 
	 * <pre>
	 * Connection con = null;
	 * PrepatedStatment pstmt = null;
	 * ResultSet rs = null;
	 * try {
	 *     con = ConnectionManager.getConnection();
	 *     pstmt = con.prepareStatement("select * from foo");
	 *     rs = psmt.executeQuery();
	 *     ....
	 * }
	 * catch (SQLException sqle) {
	 *     Log.error(sqle);
	 * }
	 * finally {
	 *     ConnectionManager.closeConnection(rs, pstmt, con);
	 * }
	 * </pre>
	 * 
	 * @param rs
	 *            要关闭的结果集.
	 * @param stmt
	 *            要关闭的 statement.
	 * @param con
	 *            要关闭的数据库连接.
	 */
	public static void closeConnection(ResultSet rs, Statement stmt, Connection con) {
		closeResultSet(rs);
		closeStatement(stmt);
		closeConnection(con);
	}

	/**
	 * 关闭 一个Statement和一个数据库连接 。如下面的例子:
	 * <p/>
	 * 
	 * <pre>
	 * Connection con = null;
	 * PrepatedStatment pstmt = null;
	 * try {
	 *     con = ConnectionManager.getConnection();
	 *     pstmt = con.prepareStatement("select * from blah");
	 *     ....
	 * }
	 * catch (SQLException sqle) {
	 *     Log.error(sqle);
	 * }
	 * finally {
	 *     DbConnectionManager.closeConnection(pstmt, con);
	 * }
	 * </pre>
	 * 
	 * @param stmt
	 *            要关闭的 statement.
	 * @param con
	 *            要关闭的数据库连接.
	 */
	public static void closeConnection(Statement stmt, Connection con) {
		closeStatement(stmt);
		closeConnection(con);
	}

	/**
	 * 关闭数据库连接（连接通常返回到连接池）。
	 * <p>
	 * 在调用此方法之前任何与数据库连接相关的任何声明应被关闭。
	 * </p>
	 * 
	 * 如下面的例子：
	 * 
	 * <pre>
	 * Connection con = null;
	 * try {
	 *     con = ConnectionManager.getConnection();
	 *     ....
	 * }
	 * catch (SQLException sqle) {
	 *     Log.error(sqle);
	 * }
	 * finally {
	 *     DbConnectionManager.closeConnection(con);
	 * }
	 * </pre>
	 * 
	 * @param con
	 *            要关闭的数据库连接.
	 */
	public static void closeConnection(Connection con) {
		if (con != null) {
			try {
				con.close();
			} catch (Exception e) {
				Log.error(e.getMessage(), e);
			}
		}
	}

	/**
	 * 如果数据库支持则创建一个 TYPE_SCROLL_INSENSITIVE类型、CONCUR_READ_ONLY 的PreparedStatement,否则返回一个正常的Statement。
	 * 
	 * @param con
	 *            数据库连接.
	 * @param sql
	 *            预编译的sql.
	 * @return a PreparedStatement
	 * @throws java.sql.SQLException
	 *             发生异常.
	 */
	public static PreparedStatement createScrollablePreparedStatement(Connection con, String sql) throws SQLException {
		if (isScrollResultsSupported()) {
			return con.prepareStatement(sql, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
		} else {
			return con.prepareStatement(sql);
		}
	}

	/**
	 * 
	 * 如果数据库驱动程序支持，则通过直接将被移动光标向前滚动结果集到指定的行数. 否则，我们通过一个手动滚动到指定位置 <tt> rs.next()/<tt> 。
	 * 
	 * @param rs
	 *            要滚动的结果集.
	 * @param rowNumber
	 *            要滚动到的行数.
	 * @throws SQLException
	 *             发生异常.
	 */
	public static void scrollResultSet(ResultSet rs, int rowNumber) throws SQLException {
		if (isScrollResultsSupported()) {
			if (rowNumber > 0) {
				// 有可能发生异常，如果发生异常手工滚动.
				try {
					rs.setFetchDirection(ResultSet.FETCH_FORWARD);
					rs.relative(rowNumber);
				} catch (SQLException e) {
					Log.error("JDBC 方法 rs.relative(rowNumber)发生错误.", e);
					for (int i = 0; i < rowNumber; i++) {
						rs.next();
					}
				}
			}
		}
		// 如果数据库驱动不支持直接滚动，将采用手工滚动.
		else {
			for (int i = 0; i < rowNumber; i++) {
				rs.next();
			}
		}
	}

	/**
	 * 返回一个限制了数据量的结果集（以startIndex开始数+ numResults）。
	 * <p>
	 * fetch size 值的大小取决于获取的JDBC 驱动程序，其大小不超过500。
	 * </p>
	 * 
	 * @param stmt
	 *            Statement.
	 * @param startIndex
	 *            要获取的第一行数据索引.
	 * @param numResults
	 *            要获取的结果集的数量.
	 */
	public static void limitRowsAndFetchSize(Statement stmt, int startIndex, int numResults) {
		final int MAX_FETCHRESULTS = 500;
		final int maxRows = startIndex + numResults;
		setMaxRows(stmt, maxRows);
		if (stmt_fetchSizeSupported) {
			if (scrollResultsSupported) {
				setFetchSize(stmt, Math.min(MAX_FETCHRESULTS, numResults));
			} else {
				setFetchSize(stmt, Math.min(MAX_FETCHRESULTS, maxRows));
			}
		}
	}

	/**
	 * 如果数据库支持，设置每次数据库驱动读取的行数大小用于缓冲,提高读取效率.否则不做任何操作。
	 * 
	 * @param stmt
	 *            要设置 fetch size 的 Statement.
	 * @param fetchSize
	 *            fetchSize.
	 */
	public static void setFetchSize(Statement stmt, int fetchSize) {
		if (stmt_fetchSizeSupported) {
			try {
				stmt.setFetchSize(fetchSize);
			} catch (Throwable t) {
				Log.error("关闭数据库 pstmt.setFetchSize(fetchSize)支持.", t);
				stmt_fetchSizeSupported = false;
			}
		}
	}

	/**
	* 返回一个 SchemaManager 实例，可以用来管理数据库信息。
	*
	* @return a SchemaManager 实例.
	*/
	public static SchemaManager getSchemaManager() {
		return schemaManager;
	}

	/**
	 * 返回当前数据库连接的提供程序。
	 * 
	 * @return 数据库连接的提供程序.
	 */
	public static ConnectionProvider getConnectionProvider() {
		return connectionProvider;
	}

	/**
	 * 设置一个新的数据库连接提供程序。
	 * <p>
	 * 旧的数据库提供程序在新的使用前会被关闭，同时新的连接池提供程序将被启动。
	 * </p>
	 * 
	 * @param provider
	 *            数据库连接的提供程序.
	 */
	public static void setConnectionProvider(ConnectionProvider provider) {
		synchronized (providerLock) {
			if (connectionProvider != null) {
				connectionProvider.destroy();
				connectionProvider = null;
			}
			connectionProvider = provider;
			connectionProvider.start();
			// 现在，获得一个连接来确定元数据信息.
			Connection con = null;
			try {
				con = connectionProvider.getConnection();
				setMetaData(con);
				// 检查数据库是否有更新.
				schemaManager.checkSchema(con);
			} catch (Exception e) {
				Log.error(e.getMessage(), e);
			} finally {
				closeConnection(con);
			}
		}
		// 保存当前的数据库连接提供程序，以便重启后继续使用设置后的新数据库连接提供程序。
		JawaGlobals.setXMLProperty("database.connectionProvider.className", provider.getClass().getName());
	}

	/**
	 * 销毁连接池提供程序。
	 * <p>
	 * 接下来调用{@link #getConnectionProvider()} 将会返回 <tt>null</tt>。 直到ConnectionProvider被设置或是通过调用{@link #getConnection()}时被设置。
	 * <p>
	 */
	public static void destroyConnectionProvider() {
		synchronized (providerLock) {
			if (connectionProvider != null) {
				connectionProvider.destroy();
				connectionProvider = null;
			}
		}
	}

	/**
	 * 如果数据库驱动支持，设置 Statement 应返回的最大行数.否则不做任何操作.
	 * 
	 * @param stmt
	 *            要设置返回的最大行数的 statement.
	 * @param maxRows
	 *            每次返回的最大行数.
	 */
	public static void setMaxRows(Statement stmt, int maxRows) {
		if (isMaxRowsSupported()) {
			try {
				stmt.setMaxRows(maxRows);
			} catch (Throwable t) {
				Log.error("关闭数据库对 JDBC 方法 stmt.setMaxRows(maxRows)的支持.", t);
				maxRowsSupported = false;
			}
		}
	}

	/**
	 * 如果数据库支持，设置每次数据库驱动读取的行数大小用于缓冲,提高读取效率.否则不做任何操作.
	 * 
	 * @param rs
	 *            要设置fetch size 的结果集.
	 * @param fetchSize
	 *            fetchSize.
	 */
	public static void setFetchSize(ResultSet rs, int fetchSize) {
		if (isFetchSizeSupported()) {
			try {
				rs.setFetchSize(fetchSize);
			} catch (Throwable t) {
				Log.error("关闭数据库对 JDBC 方法 rs.setFetchSize(fetchSize)的支持.", t);
				fetchSizeSupported = false;
			}
		}
	}

	/**
	 * 从数据库中使用一个连接来设置元数据信息.用于支持不同的JDBC驱动程序和数据库.
	 * 
	 * @param con
	 *            数据库连接.
	 * @throws SQLException
	 *             发生异常.
	 */
	private static void setMetaData(Connection con) throws SQLException {
		DatabaseMetaData metaData = con.getMetaData();
		// 是否支持事务
		transactionsSupported = metaData.supportsTransactions();
		// 是否支持子查询
		subqueriesSupported = metaData.supportsCorrelatedSubqueries();
		// 是否支持滚动的结果集
		try {
			scrollResultsSupported = metaData.supportsResultSetType(ResultSet.TYPE_SCROLL_INSENSITIVE);
		} catch (Exception e) {
			scrollResultsSupported = false;
		}
		// 是否支持批量更新
		batchUpdatesSupported = metaData.supportsBatchUpdates();

		// 设置默认的元数据信息
		streamTextRequired = false;
		maxRowsSupported = true;
		fetchSizeSupported = true;

		// 通过数据库名称来设置元数据属性.
		String dbName = metaData.getDatabaseProductName().toLowerCase();
		String driverName = metaData.getDriverName().toLowerCase();

		// Oracle
		if (dbName.indexOf("oracle") != -1) {
			databaseType = DatabaseType.oracle;
			streamTextRequired = true;
			scrollResultsSupported = false;
			if (driverName.indexOf("auguro") != -1) {
				streamTextRequired = false;
				fetchSizeSupported = true;
				maxRowsSupported = false;
			}
		}
		// Postgres
		else if (dbName.indexOf("postgres") != -1) {
			databaseType = DatabaseType.postgresql;
			scrollResultsSupported = false;
			fetchSizeSupported = false;
		}
		// Interbase
		else if (dbName.indexOf("interbase") != -1) {
			databaseType = DatabaseType.interbase;
			fetchSizeSupported = false;
			maxRowsSupported = false;
		}
		// SQLServer
		else if (dbName.indexOf("sql server") != -1) {
			databaseType = DatabaseType.sqlserver;
			if (driverName.indexOf("una") != -1) {
				fetchSizeSupported = true;
				maxRowsSupported = false;
			}
		}
		// MySQL
		else if (dbName.indexOf("mysql") != -1) {
			databaseType = DatabaseType.mysql;
			transactionsSupported = false;
		}
		// HSQL
		else if (dbName.indexOf("hsql") != -1) {
			databaseType = DatabaseType.hsqldb;
		}
		// DB2
		else if (dbName.indexOf("db2") != 1) {
			databaseType = DatabaseType.db2;
		}
	}

	/**
	 * 
	 * 返回数据库类型,是一个常量.
	 * 
	 * @return 数据库类型.
	 */
	public static DatabaseType getDatabaseType() {
		return databaseType;
	}

	public static boolean isTransactionsSupported() {
		return transactionsSupported;
	}

	public static boolean isStreamTextRequired() {
		return streamTextRequired;
	}

	public static boolean isMaxRowsSupported() {
		return maxRowsSupported;
	}

	public static boolean isFetchSizeSupported() {
		return fetchSizeSupported;
	}

	public static boolean isStmtFetchSizeSupported() {
		return stmt_fetchSizeSupported;
	}

	public static boolean isSubqueriesSupported() {
		return subqueriesSupported;
	}

	public static boolean isScrollResultsSupported() {
		return scrollResultsSupported;
	}

	public static boolean isBatchUpdatesSupported() {
		return batchUpdatesSupported;
	}

	public static String getTestSQL(String driver) {
		if (driver == null) {
			return "select 1";
		} else if (driver.contains("db2")) {
			return "select 1 from sysibm.sysdummy1";
		} else if (driver.contains("oracle")) {
			return "select 1 from dual";
		} else {
			return "select 1";
		}
	}

	/**
	 * 数据库类型。
	 */
	public static enum DatabaseType {

		oracle,

		postgresql,

		mysql,

		hsqldb,

		db2,

		sqlserver,

		interbase,

		unknown;
	}

	private DbConnectionManager() {
		// 不可实例化.
	}
}